/**
* \file DGVVolumePlotVTK.cpp
*
* \brief This file defines a class for display Volume Plots using VTK.
* \author Shekhar S. Chandra, 2008
*
* This file is part of DGV Library.
*
* DGV is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* DGV is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with DGV.  If not, see <http://www.gnu.org/licenses/>.
*/
#include "DGVVolumePlotVTK.h"

#include "vtkProperty2D.h"
#include "vtkTextProperty.h"
#include "vtkXMLImageDataWriter.h"
#include "vtkStructuredPointsWriter.h"

DGVVolumePlotVTK::DGVVolumePlotVTK(QWidget *parent) : QVTKWidget(parent)
{
    loaded = false;
    externalData = false;
    unsignedCharMode = false;
    mapper = false;
    generated = false;
    axisGenerated = false;
    closestTriad = false;
    antiAliasing = false;
    imageDataSource = false;
}

DGVVolumePlotVTK::~DGVVolumePlotVTK()
{
//    if(loaded)
//    {
//        if(!externalData)
//            plotData->Delete();
//        if(generated)
//        {
//            opacityTransferFunction->Delete(); //!< Control Transparency
//            colorTransferFunction->Delete(); //!< Colour map
//            volumeProperty->Delete(); //!< Encapsulates transparency and colour
//            if(mapper)
//            {
//                if(!unsignedCharMode)
//                    volumeMapper_Fixed->Delete(); //!< Maps data to grid volume
//                else
//                {
//                    volumeMapper->Delete(); //!< Maps data to grid volume using unsigned chars.
//                    compositeFunction->Delete();
//                }
//            }
//            volume->Delete(); //!< Actual volume actor
//            renderer->Delete(); //!< Renderer of the plot
//            renderWin->Delete(); //!< Window that controls the renderer
//            if(axisGenerated)
//                axes->Delete(); //!< Axis for plot
//        }
//    }
    ///Smart Deletion on
    ///
}

void DGVVolumePlotVTK::setName(QString filename)
{
    name = filename;
    setWindowTitle(strippedNamePrefix());

    emit nameChanged(strippedNamePrefix());
}

void DGVVolumePlotVTK::setData(Array<imageType,3> &data)
{
    plotArray.resizeAndPreserve(data.shape());
    plotArray = data;

    plotData = Blitz.arrayToVTKImageData(data);
    plotData->Update();

    loaded = true;
    imageDataSource = false;
    cerr << "VPlot Min: " << Blitz.getMin() << ", Data Max: " << Blitz.getMax() << ". Mean: " << Blitz.getMean() << endl;
}

void DGVVolumePlotVTK::setData(Array<complex<imageType>,3> &data)
{
    plotArray.resizeAndPreserve(data.shape());
    plotArray = abs(data);

    plotData = Blitz.arrayToVTKImageData(data);
    plotData->Update();

    loaded = true;
    imageDataSource = false;
    cerr << "VPlot Min: " << Blitz.getMin() << ", Data Max: " << Blitz.getMax() << ". Mean: " << Blitz.getMean() << endl;
}

void DGVVolumePlotVTK::setData(vtkImageData *data)
{
    int bounds[6];

    data->GetExtent(bounds);

    if(bounds[1] > 0 && bounds[3] > 0 && bounds[5] > 0)
    {
        plotData = data;
        plotData->SetExtent(bounds);
        externalData = true;
        plotData->Update();
        Blitz.vtkImageDataToArray(plotData,plotArray);
        imageDataSource = true;
        loaded = true;
    }
}

void DGVVolumePlotVTK::SetInputConnection(vtkAlgorithmOutput *connection)
{
    inputConnection = connection;
    if(!unsignedCharMode)
    {
        volumeMapper_Fixed = PlotFixedRayCastMapper::New();
        volumeMapper_Fixed->SetInputConnection(connection);
    }
    else
    {
        compositeFunction = PlotCompositeFunction::New();
        volumeMapper = PlotRayCastMapper::New();
            volumeMapper->SetVolumeRayCastFunction(compositeFunction);
            volumeMapper->SetInputConnection(connection);
    }
    mapper = true;
}

void DGVVolumePlotVTK::generatePlot()
{
    if(loaded)
    {
        int bounds[6];

        if(imageDataSource)
            Blitz.vtkImageDataToArray(plotData,plotArray);

        plotData->GetExtent(bounds);

        opacityTransferFunction = PlotPiecewiseFunction::New();
            opacityTransferFunction->AddPoint(Blitz.getMin(), 0.0);
            opacityTransferFunction->AddPoint(Blitz.getMax(), 0.2);

        colorTransferFunction = PlotColorTransferFunction::New();
            colorTransferFunction->AddRGBPoint(Blitz.getMin(), 0.0, 0.0, 0.0);
            colorTransferFunction->AddRGBPoint(Blitz.getMax()/4, 1.0, 0.0, 0.0);
            colorTransferFunction->AddRGBPoint(Blitz.getMax()/2, 0.0, 0.0, 1.0);
            colorTransferFunction->AddRGBPoint(3*Blitz.getMax()/4, 0.0, 1.0, 0.0);
            colorTransferFunction->AddRGBPoint(Blitz.getMax(), 0.0, 0.2, 0.0);

        volumeProperty = PlotVolumeProperty::New();
            volumeProperty->SetColor(colorTransferFunction);
            volumeProperty->SetScalarOpacity(opacityTransferFunction);
            volumeProperty->ShadeOn();
            //volumeProperty->SetInterpolationTypeToLinear();

        volume = PlotVolume::New();
        if(!unsignedCharMode)
        {
            if(!mapper)
            {
                volumeMapper_Fixed = PlotFixedRayCastMapper::New();
                volumeMapper_Fixed->SetInput(plotData);
                mapper = true;
            }
            volume->SetMapper(volumeMapper_Fixed);
        }
        else
        {
            if(!mapper)
            {
                compositeFunction = PlotCompositeFunction::New();
                volumeMapper = PlotRayCastMapper::New();
                    volumeMapper->SetVolumeRayCastFunction(compositeFunction);
                    volumeMapper->SetInput(plotData);
                mapper = true;
            }
            volume->SetMapper(volumeMapper);
        }
        volume->SetProperty(volumeProperty);

        renderer = PlotRenderer::New();
            renderer->AddVolume(volume);
            renderer->ResetCamera();
            renderer->SetBackground(0.1, 0.2, 0.4);
            renderer->ResetCameraClippingRange();

        renderWin = PlotRenderWindow::New();
            renderWin->AddRenderer(renderer);

        setupEvents();

        QVTKWidget::SetRenderWindow(renderWin);
        if(bounds[1] > minWindowSize || bounds[3] > minWindowSize)
        {
            renderWin->SetSize(bounds[1],bounds[3]);
            QVTKWidget::resize(bounds[1],bounds[3]);
        }
        else
        {
            renderWin->SetSize(minWindowSize,minWindowSize);
            QVTKWidget::resize(minWindowSize,minWindowSize);
        }
        generated = true;
    }
}

void DGVVolumePlotVTK::generateAxes()
{
    if(loaded && generated)
    {
        double ranges[6];
        int bounds[6];
        axes = PlotAxes::New();

        ///Axes
        axes->SetInput(plotData);
        axes->SetCamera(renderer->GetActiveCamera());
        axes->SetLabelFormat("%6.4g");
        if(!closestTriad)
            axes->SetFlyModeToOuterEdges();
        else
            axes->SetFlyModeToClosestTriad();
        axes->SetFontFactor(0.8);

        ///Ensure z-scale is true
        plotData->GetExtent(bounds);
        axes->UseRangesOn();
        ranges[0] = 1.0;
        ranges[1] = static_cast<double>(bounds[1]+1);
        ranges[2] = 1.0;
        ranges[3] = static_cast<double>(bounds[3]+1);
        ranges[4] = 1.0;
        ranges[5] = static_cast<double>(bounds[5]+1);
        axes->SetRanges(ranges);

        renderer->AddViewProp(axes);
        renderer->ResetCamera();
        renderer->SetBackground(0.1, 0.2, 0.4);
        renderer->ResetCameraClippingRange();
        renderWin->AddRenderer(renderer);
        QVTKWidget::SetRenderWindow(renderWin);

        axisGenerated = true;
    }
}

void DGVVolumePlotVTK::setXLabel(QString text)
{
    if(axisGenerated)
    {
        axes->SetXLabel(text.toStdString().c_str());
    }
}

void DGVVolumePlotVTK::setYLabel(QString text)
{
    if(axisGenerated)
    {
        axes->SetYLabel(text.toStdString().c_str());
    }
}

void DGVVolumePlotVTK::setZLabel(QString text)
{
    if(axisGenerated)
    {
        axes->SetZLabel(text.toStdString().c_str());
    }
}

void DGVVolumePlotVTK::setBackground(double r, double g, double b)
{
    if(loaded && generated)
    {
        renderer->SetBackground(r, g, b);
        renderWin->Render();
    }
}

void DGVVolumePlotVTK::setAxesAsClosestTriad()
{
    if(axisGenerated)
    {
        axes->SetFlyModeToClosestTriad();
    }
}

void DGVVolumePlotVTK::setAxesAsOuterEdges()
{
    if(axisGenerated)
    {
        axes->SetFlyModeToOuterEdges();
    }
}

bool DGVVolumePlotVTK::saveVTI(const QString filename)
{
    if(loaded && generated)
    {
        vtkXMLImageDataWriter* writer = vtkXMLImageDataWriter::New();

        writer->SetInput(plotData);
        writer->SetFileName(filename.toStdString().c_str());
        writer->SetDataModeToBinary();
        writer->Write();
        writer->Delete();

        return true;
    }
    else
        return false;
}

bool DGVVolumePlotVTK::saveVTK(const QString filename)
{
    if(loaded && generated)
    {
        vtkStructuredPointsWriter* writer = vtkStructuredPointsWriter::New();

        writer->SetInput(plotData);
        writer->SetFileName(filename.toStdString().c_str());
        writer->Write();
        writer->Delete();

        return true;
    }
    else
        return false;
}

QString DGVVolumePlotVTK::strippedName()
{
    return QFileInfo(name).fileName();
}

QString DGVVolumePlotVTK::strippedNamePrefix()
{
    return "VPlot: " + QFileInfo(name).fileName();
}

void DGVVolumePlotVTK::scanVolume()
{
    if(loaded && generated)
    {
        bool ok;
        DGVSurfacePlotVTK *splot = new DGVSurfacePlotVTK(parentWidget());

        double interval = QInputDialog::getDouble(this, tr("Scan Interval Time"), tr("Interval (ms):"), 250, -100000, 100000, 1, &ok);

        if(ok)
        {
            splot->setName("Surface Animation");
            splot->autoScaleSurface(true);
            splot->animateFromSlices(&plotArray,interval);
            splot->show();

            emit surfacePlotAvailable(splot);
        }
    }
}

void DGVVolumePlotVTK::xLabel()
{
    bool ok;
    QString text = QInputDialog::getText(this, tr("x-axis Label"),
                                          tr("New Label:"), QLineEdit::Normal,
                                          "x", &ok);
    if(ok)
        setXLabel(text);
}

void DGVVolumePlotVTK::yLabel()
{
    bool ok;
    QString text = QInputDialog::getText(this, tr("y-axis Label"),
                                          tr("New Label:"), QLineEdit::Normal,
                                          "y", &ok);
    if(ok)
        setYLabel(text);
}

void DGVVolumePlotVTK::zLabel()
{
    bool ok;
    QString text = QInputDialog::getText(this, tr("z-axis Label"),
                                          tr("New Label:"), QLineEdit::Normal,
                                          "z", &ok);
    if(ok)
        setZLabel(text);
}

void DGVVolumePlotVTK::opacity()
{
    if(generated)
    {
        bool ok1, ok2;

        if(imageDataSource)
            Blitz.vtkImageDataToArray(plotData,plotArray);

        double min = QInputDialog::getDouble(this, tr("Opacity"), tr("Opacity Min: "), Blitz.getMin(), Blitz.getMin(), Blitz.getMax(), 2, &ok1);
        double max = QInputDialog::getDouble(this, tr("Opacity"), tr("Opacity Max: "), Blitz.getMax(), Blitz.getMin(), Blitz.getMax(), 2, &ok2);

        if(ok1 && ok2)
        {
            opacityTransferFunction->Initialize();
            opacityTransferFunction->AddPoint(min, 0.0);
            opacityTransferFunction->AddPoint(max, 0.2);
            volume->Update();
            renderWin->Render();
        }
    }
}

void DGVVolumePlotVTK::background()
{
    double colours[3];
    bool ok1, ok2, ok3;

    if(generated)
    {
        if(imageDataSource)
            Blitz.vtkImageDataToArray(plotData,plotArray);

        renderer->GetBackground(colours);
        double red = QInputDialog::getDouble(this, tr("New Background Colour"), tr("Red Component: "), colours[0], 0, 255, 2, &ok1);
        double green = QInputDialog::getDouble(this, tr("New Background Colour"), tr("Green Component: "), colours[1], 0, 255, 2, &ok2);
        double blue = QInputDialog::getDouble(this, tr("New Background Colour"), tr("Blue Component: "), colours[2], 0, 255, 2, &ok3);

        if(ok1 && ok2 && ok3)
            setBackground(red,green,blue);
    }
}

void DGVVolumePlotVTK::axesProperties()
{
    bool ok1, ok2, ok3, ok4;

    if(axisGenerated)
    {
        double colours[3], factor = 1.0;
        vtkProperty2D *properties = axes->GetProperty();
        vtkTextProperty *txtProperties = axes->GetAxisTitleTextProperty();

        properties->GetColor(colours);
        factor = axes->GetFontFactor();
        double red = QInputDialog::getDouble(this, tr("New Axis Colour"), tr("Red Component (0.0-1.0): "), colours[0], 0, 1, 2, &ok1);
        double green = QInputDialog::getDouble(this, tr("New Axis Colour"), tr("Green Component (0.0-1.0): "), colours[1], 0, 1, 2, &ok2);
        double blue = QInputDialog::getDouble(this, tr("New Axis Colour"), tr("Blue Component (0.0-1.0): "), colours[2], 0, 1, 2, &ok3);
        factor = QInputDialog::getDouble(this, tr("New Axis Font Factor"), tr("Font Factor: "), factor, 0, 100, 3, &ok4);

        if(ok1 && ok2 && ok3 && ok4)
        {
            properties->SetColor(red,green,blue);
            txtProperties->ShadowOff();
            txtProperties->SetColor(red,green,blue);
            axes->SetFontFactor(factor);
            axes->SetProperty(properties);
            axes->SetAxisTitleTextProperty(txtProperties);
            axes->SetAxisLabelTextProperty(txtProperties);
            renderWin->Render();
        }
    }
}

void DGVVolumePlotVTK::toggleAxesAsClosestTriad()
{
    if(axisGenerated)
    {
        closestTriad = triadAct->isChecked();
        if(closestTriad)
            setAxesAsClosestTriad();
        else
            setAxesAsOuterEdges();
    }
}

void DGVVolumePlotVTK::toggleAntiAliasing()
{
    if(generated)
    {
        antiAliasing = antiAliasingAct->isChecked();
        if(antiAliasing)
        {
            renderWin->SetAAFrames(maxAASamples);
            renderWin->Render();
        }
        else
        {
            renderWin->SetAAFrames(0);
            renderWin->Render();
        }
    }
}

void DGVVolumePlotVTK::save()
{
    if(loaded && generated)
    {
        QFileDialog *fileSaver = new QFileDialog(this);
        QSettings settings("Shakes", "DGV");

        QString path = settings.value("recentPath").toString();
        QString filename = fileSaver->getSaveFileName(this,
                                       tr("Select File Name to Save"),
                                       path,
                                       tr("VTK Files (*.vti *.vtk)"));

        if(!filename.isEmpty())
        {
            QFileInfo fi(filename);
            QString extension = fi.suffix().toLower();

            if(extension == "vtk")
                saveVTK(filename);
            else
                saveVTI(filename);
        }
    }
}

void DGVVolumePlotVTK::renameData()
{
    bool ok;

    QString newName = QInputDialog::getText(this, tr("Rename Data"), tr("New Name: "), QLineEdit::Normal, name, &ok);

    if(ok)
        setName(newName);
}

void DGVVolumePlotVTK::setupEvents()
{
    ///Unbind the right mouse button events as Qt uses context menu.
    QVTKWidget::GetRenderWindow()->GetInteractor()->RemoveObservers(vtkCommand::RightButtonPressEvent);
    QVTKWidget::GetRenderWindow()->GetInteractor()->RemoveObservers(vtkCommand::RightButtonReleaseEvent);
}

void DGVVolumePlotVTK::contextMenuEvent(QContextMenuEvent *event)
{
    contextMenu = new QMenu(this); //!< Only exists for the duration of the context selection

    ///Global Ops
    surfaceAct = contextMenu->addAction(tr("&Scan Volume with Surfaces"));
    surfaceAct->setShortcut(tr("Ctrl+s"));
    connect(surfaceAct, SIGNAL(triggered()), this, SLOT(scanVolume()));
    contextMenu->addSeparator()->setText("Axes");
    ///Labels and Options
    xLabelAct = contextMenu->addAction(tr("Set &x Label"));
    xLabelAct->setShortcut(tr("Ctrl+x"));
    connect(xLabelAct, SIGNAL(triggered()), this, SLOT(xLabel()));
    yLabelAct = contextMenu->addAction(tr("Set &y Label"));
    yLabelAct->setShortcut(tr("Ctrl+y"));
    connect(yLabelAct, SIGNAL(triggered()), this, SLOT(yLabel()));
    zLabelAct = contextMenu->addAction(tr("Set &z Label"));
    zLabelAct->setShortcut(tr("Ctrl+z"));
    connect(zLabelAct, SIGNAL(triggered()), this, SLOT(zLabel()));
    opacityAct = contextMenu->addAction(tr("Set &Opacity Level"));
    opacityAct->setShortcut(tr("Ctrl+o"));
    connect(opacityAct, SIGNAL(triggered()), this, SLOT(opacity()));
    backgroundAct = contextMenu->addAction(tr("Set &Background Colour"));
    backgroundAct->setShortcut(tr("Ctrl+b"));
    connect(backgroundAct, SIGNAL(triggered()), this, SLOT(background()));
    axesAct = contextMenu->addAction(tr("Set &Axes Colour/Font"));
    axesAct->setShortcut(tr("Ctrl+a"));
    connect(axesAct, SIGNAL(triggered()), this, SLOT(axesProperties()));
    ///Setup Toggle Options
    triadAct = contextMenu->addAction(tr("Axes as Closest Triad"));
    triadAct->setCheckable(true);
    triadAct->setChecked(closestTriad);
    connect(triadAct, SIGNAL(triggered()), this, SLOT(toggleAxesAsClosestTriad()));
    antiAliasingAct = contextMenu->addAction(tr("Anti-Aliasing"));
    antiAliasingAct->setCheckable(true);
    antiAliasingAct->setChecked(antiAliasing);
    connect(antiAliasingAct, SIGNAL(triggered()), this, SLOT(toggleAntiAliasing()));
    contextMenu->addSeparator();
	///Setup Close action
	///Save
	saveAct = contextMenu->addAction(tr("Save Volume Data..."));
	saveAct->setShortcut(tr("Ctrl+s"));
	connect(saveAct, SIGNAL(triggered()), this, SLOT(save()));
	renameAct = contextMenu->addAction(tr("Rename"));
	renameAct->setShortcut(tr("Ctrl+r"));
	connect(renameAct, SIGNAL(triggered()), this, SLOT(renameData()));
	///Close
	closeAct = contextMenu->addAction(tr("Close"));
	closeAct->setShortcut(tr("Ctrl+q"));
	connect(closeAct, SIGNAL(triggered()), this, SLOT(close()));

	contextMenu->exec(event->globalPos());
}
